package org.argus.common.web.core.security;

import lombok.extern.slf4j.Slf4j;
import org.argus.common.core.constant.TokenConstants;
import org.argus.common.core.token.ITokenManager;
import org.argus.common.core.utils.StringUtils;
import org.argus.common.core.uuid.IdUtils;
import org.argus.common.redis.service.RedisService;
import org.argus.common.web.core.constants.CacheConstants;
import org.argus.common.web.core.constants.SecurityConstants;
import org.argus.common.web.core.domain.LoginUser;
import org.argus.common.web.core.utils.ServletUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.concurrent.TimeUnit;

/**
 * 令牌管理器
 * 
 * @Author 刘文/Cliff Liu
 * @Date 2024/5/4
 */
@Component
@Slf4j
public class TokenManager implements ITokenManager {

    @Autowired
    private RedisService redisService;

    @Autowired
    private JwtMaintainer jwtMaintainer;

    /**
     * 刷新令牌有效期，默认七天
     */
    @Value("${argus.refreshToken.keepTimeMils:604800000}")
    private long refreshTokenKeepTimeMils;

    /**
     * 访问令牌有效期
     */
    private static final long ACCESS_TOKEN_EXPIRE = 20 * 60 * 1000;

    private static final String ADMIN_USERNAME = "admin";

    private static final Long ADMIN_USER_ID = 1L;

    public void initToken(LoginUser loginUser) {
        String refreshToken = IdUtils.randomUUID();
        loginUser.setRefreshToken(refreshToken);
        loginUser.setExpires(System.currentTimeMillis() + ACCESS_TOKEN_EXPIRE);
        String accessToken = jwtMaintainer.generateAccessToken(loginUser);
        loginUser.setAccessToken(accessToken);
        redisService.setCacheObject(getAccessTokenKey(loginUser.getUsername(), accessToken), refreshToken,
            ACCESS_TOKEN_EXPIRE, TimeUnit.MILLISECONDS);
        redisService.setCacheObject(getRefreshTokenKey(loginUser.getUsername(), refreshToken), loginUser,
            refreshTokenKeepTimeMils, TimeUnit.MILLISECONDS);
    }

    /**
     * 获取访问令牌key
     * 
     * @param username
     * @param accessToken
     * @return
     */
    public static String getAccessTokenKey(String username, String accessToken) {
        return SecurityConstants.ACCESS_TOKEN_PREFIX + username + CacheConstants.SEPARATOR + accessToken;
    }

    /**
     * 获取刷新令牌key
     * 
     * @param username
     * @param refreshToken
     * @return
     */
    public static String getRefreshTokenKey(String username, String refreshToken) {
        return SecurityConstants.REFRESH_TOKEN_PREFIX + username + CacheConstants.SEPARATOR + refreshToken;
    }

    /**
     * 获取请求发起用户名
     * 
     * @return
     */
    public String getUsername() {
        String accessToken = getAccessToken();
        if (StringUtils.isNotBlank(accessToken)) {
            return jwtMaintainer.getUsername(accessToken);
        }
        return ADMIN_USERNAME;
    }

    @Override
    public Long getUserCurrentDomainId() {
        String accessToken = getAccessToken();
        if (StringUtils.isNotBlank(accessToken)) {
            return jwtMaintainer.getDomainId(accessToken);
        }
        return null;
    }

    /**
     * 获取请求发起用户id
     *
     * @return
     */
    public Long getUserId() {
        String accessToken = getAccessToken();
        if (StringUtils.isNotBlank(accessToken)) {
            return jwtMaintainer.getUserId(accessToken);
        }
        log.trace("Using admin for cannot get user id from jwt");
        return ADMIN_USER_ID;
    }

    public String getUserType() {
        return jwtMaintainer.getUserType(getAccessToken());
    }

    private static String getAccessToken() {
        HttpServletRequest httpServletRequest = ServletUtils.getRequest();
        String accessToken = ServletUtils.getHeader(httpServletRequest, SecurityConstants.AUTH_HEADER);
        if(StringUtils.isNotBlank(accessToken)){
            return replaceTokenPrefix(accessToken);
        }
        return StringUtils.EMPTY;
    }

    /**
     * 裁剪token前缀
     */
    public static String replaceTokenPrefix(String token) {
        // 如果前端设置了令牌前缀，则裁剪掉前缀
        if (StringUtils.isNotEmpty(token) && token.startsWith(TokenConstants.PREFIX)) {
            token = token.replaceFirst(TokenConstants.PREFIX, "");
        }
        return token;
    }

    public LoginUser refreshToken(String refreshToken, String username) {
        String refreshTokenKey = getRefreshTokenKey(username, refreshToken);
        LoginUser loginUser = redisService.getCacheObject(refreshTokenKey);
        if (loginUser != null) {
            String accessToken = jwtMaintainer.generateAccessToken(loginUser);
            loginUser.setAccessToken(accessToken);
            loginUser.setExpires(System.currentTimeMillis() + ACCESS_TOKEN_EXPIRE);
            redisService.setCacheObject(getAccessTokenKey(username, accessToken), refreshToken, ACCESS_TOKEN_EXPIRE,
                TimeUnit.MILLISECONDS);
            // 延长refresh token 过期时间
            redisService.expire(refreshTokenKey, refreshTokenKeepTimeMils, TimeUnit.MILLISECONDS);
            return loginUser;
        }
        return null;
    }

    /**
     * 退出登录时清除用户相关的缓存 token
     */
    public String clearTokens() {
        String accessToken = getAccessToken();
        if (accessToken == null) {
            return null;
        }
        String username = jwtMaintainer.getUsername(accessToken);
        String accessTokenKey = getAccessTokenKey(username, accessToken);
        String refreshToken = redisService.getCacheObject(accessTokenKey);
        redisService.deleteObject(accessTokenKey);
        redisService.deleteObject(getRefreshTokenKey(username, refreshToken));
        return username;
    }

    public LoginUser switchDomain(Long newDomainId) {
        String accessToken = getAccessToken();
        String username = jwtMaintainer.getUsername(accessToken);
        String accessTokenKey = getAccessTokenKey(username, accessToken);
        String refreshToken = redisService.getCacheObject(accessTokenKey);
        String refreshTokenKey = getRefreshTokenKey(username, refreshToken);
        LoginUser loginUser = redisService.getCacheObject(refreshTokenKey);
        loginUser.setDomainId(newDomainId);
        redisService.setCacheObject(refreshTokenKey, loginUser, refreshTokenKeepTimeMils, TimeUnit.MILLISECONDS);
        accessToken = jwtMaintainer.generateAccessToken(loginUser);
        loginUser.setAccessToken(accessToken);
        loginUser.setExpires(System.currentTimeMillis() + ACCESS_TOKEN_EXPIRE);
        redisService.setCacheObject(getAccessTokenKey(loginUser.getUsername(), accessToken), refreshToken,
                ACCESS_TOKEN_EXPIRE, TimeUnit.MILLISECONDS);
        return loginUser;
    }
}
